#include "usb_monitor.h"

#include <iostream>
#include <vector>
#include <aio.h>     // async I/O
#include <fcntl.h>   // open() file descriptor
#include <libudev.h> // apt-get install libudev-dev
#include <libusb-1.0/libusb.h>  // apt-get install libusb-1.0-0-dev
#include <boost/thread.hpp>
#include <boost/algorithm/string/replace.hpp>

namespace dmcg {
namespace module {
namespace libusb {
class UsbMonitorPrivate
{
public:
    UsbMonitorPrivate(UsbMonitor *o)
    {
        this->owner = o;
        this->running = true;
        this->context = nullptr;
    }

    // 初始化
    int init();

    // 退出
    int uninit();

    // 移除某个设备
    int remove_device(const usb_device &udev, const std::string &currentusername);

    // 移除所有设备
    int remove_all_device(const std::string &currentusername);

    // 查找某个设备
    bool find_device(const usb_device &);

    // 获取当前USB设备数
    int count_devices();

private:
    // 枚举当前已插入设备
    int enumerate_devices();

    // 实时监控插、拔设备
    int monitor_devices();

    // 插拔信号
    int device_hotplug_arrived(const usb_device &);
    int device_hotplug_left(udev_device *dev);

    // USB设备详情
    usb_device libusb_device_detail(libusb_device *);
    usb_device libusb_device_detail(udev_device *);

    // 热插拔设备路径
    std::string hotplug_device_path(const usb_device &);

    // 移除热插设备
    usb_device remove_hotplug_device(udev_device *dev);
    int remove_hotplug_device(const usb_device &);

    // 移除某个block设备
    int remove_block_device(const usb_device &);

    // 移除某个低速设备
    int remove_lowspeed_device(const usb_device &, const std::string &currentusername);

    // 根据序列号等获取设备详情
    usb_device find_usb_device(const usb_device &);

private:
    bool running;
    boost::mutex udevMutex;
    UsbMonitor *owner;
    libusb_context *context;
    std::vector<usb_device> hotplugDevices;
};

UsbMonitor::UsbMonitor()
    : d(new UsbMonitorPrivate(this))
{
}

UsbMonitor::~UsbMonitor()
{
}

void UsbMonitor::operator()()
{
    d->init();
}

int UsbMonitor::stop_monitor()
{
    return d->uninit();
}

int UsbMonitor::remove_device(const usb_device &udev, const std::string &currentusername)
{
    return d->remove_device(udev, currentusername);
}

int UsbMonitor::remove_all_device(const std::string &currentusername)
{
    return d->remove_all_device(currentusername);
}

bool UsbMonitor::find_device(const usb_device &udev)
{
    return d->find_device(udev);
}

int UsbMonitor::count_devices()
{
    return d->count_devices();
}

int UsbMonitorPrivate::init()
{
    int ret = libusb_init(&context);
    if (ret < 0) {
        std::cout << "initialise libusb failed: " << libusb_error_name(ret);
        return -1;
    }

    if (0 == this->enumerate_devices()) {
        this->monitor_devices();
    }

    libusb_exit(context);
    return 0;
}

int UsbMonitorPrivate::uninit()
{
    this->running = false;
    return 0;
}

int UsbMonitorPrivate::enumerate_devices()
{
    std::cout << "Begin enumerating usb devices.";

    // 获取当前USB设备列表
    libusb_device **devs = nullptr;
    int ret = libusb_get_device_list(context, &devs);
    if (ret < 0) {
        std::cout << "enumerate usb device failed: " << libusb_error_name(ret);
        return -1;
    }

    // 获取USB设备信息
    std::vector<usb_device> devices;
    for (int i = 0; i < ret; i++) {
        usb_device udev = libusb_device_detail(devs[i]);
        if (udev.is_storage_device() || udev.is_lowspeed_device()) {
            devices.push_back(udev);
        }
    }

    // 对用户发出通知
    if (!devices.empty()) {
        std::cout << "emit exists devices [add action]: count=" << devices.size();
        this->owner->UsbDeviceAction(USB_ADD_ACT, devices);
    }

    // USB设备列表销毁
    libusb_free_device_list(devs, 1);
    std::cout << "End enumerating usb devices.";
    return 0;
}

int UsbMonitorPrivate::monitor_devices()
{
    std::cout << "Start monitoring usb devices.";

    static usb_device last_usb_device;
    static std::string last_device_interclass;
    static bool hot_plug_flag = false;

    /* Create the udev object */
    struct udev *udev = udev_new();
    if (!udev) {
        std::cout << "Can't create udev.";
        return -1;
    }

    struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
    udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", nullptr);
    udev_monitor_enable_receiving(mon);

    int fd = udev_monitor_get_fd(mon);

    while (this->running) {
        fd_set fds;
        FD_ZERO(&fds);
        FD_SET(fd, &fds);

        // Wait for monitor to return
        int ret = select(fd + 1, &fds, nullptr, nullptr, nullptr);
        if (ret <= 0) {
            break;
        }

        if (FD_ISSET(fd, &fds)) {
            struct udev_device *dev = udev_monitor_receive_device(mon);
            if (dev) {
                const char *bInterfaceClass = udev_device_get_sysattr_value(dev, DEVICE_INTERFACE_CLASS);
                if (bInterfaceClass) {
                    std::cout << "@@@" << bInterfaceClass;
                    last_device_interclass = bInterfaceClass;
                }

                const char *act = udev_device_get_action(dev);
                if (act) {
                    if (0 == strcmp(act, USB_REMOVE_ACT)) { // 设备拔出
                        device_hotplug_left(dev);
                        if (hot_plug_flag) {
                            last_device_interclass.clear();
                            last_usb_device.clear();
                            hot_plug_flag = false;
                        }
                    } else if (0 == strcmp(act, USB_BIND_ACT)) { // 设备插入
                        if (last_usb_device.name.empty() && udev_device_get_devnode(dev)) {
                            last_usb_device = libusb_device_detail(dev);
                        }
                    }
                }

                if (!last_usb_device.name.empty() && !last_device_interclass.empty()) {
                    last_usb_device.interfaceClass = strtoll(last_device_interclass.c_str(), nullptr, 16);
                    std::cout << "@@@" << last_usb_device.name << ", " << last_usb_device.dpath << ", " << last_usb_device.interfaceClass;
                    if (last_usb_device.is_storage_device() || last_usb_device.is_lowspeed_device()) {
                        device_hotplug_arrived(last_usb_device);
                        hot_plug_flag = true;
                    }

                    last_usb_device.clear();
                    last_device_interclass.clear();
                }

                udev_device_unref(dev);
            }
        }
    }

    udev_unref(udev);
    std::cout << "Stop monitoring usb devices.";
    return 0;
}

int UsbMonitorPrivate::device_hotplug_arrived(const usb_device &udev)
{
    std::cout << "emit [ADD] action, "
                << "Product ID: " << udev.idProduct
                << ", Vendor ID: " << udev.idVendor
                << ", serial: " << udev.serial
                << ", manufacturer: " << udev.manufacturer
                << ", product: " << udev.product;
    this->owner->UsbDeviceAction(USB_ADD_ACT, {udev});
    boost::mutex::scoped_lock lock(this->udevMutex);
    hotplugDevices.push_back(udev);
    return 0;
}

int UsbMonitorPrivate::device_hotplug_left(udev_device *udev)
{
    usb_device find_udev = remove_hotplug_device(udev);
    if (find_udev.name.empty()) {
        return -1;
    }

    std::vector<usb_device> devices;
    devices.push_back(find_udev);
    std::cout << "emit [REMOVE] action, "
                << "Product ID: " << find_udev.idProduct
                << ", Vendor ID: " << find_udev.idVendor
                << ", serial: " << find_udev.serial
                << ", manufacturer: " << find_udev.manufacturer
                << ", product: " << find_udev.product;
    this->owner->UsbDeviceAction(USB_REMOVE_ACT, devices);
    return 0;
}

usb_device UsbMonitorPrivate::libusb_device_detail(libusb_device *dev)
{
    usb_device udev;

    // 设备描述符
    struct libusb_device_descriptor dev_desc;
    int ret = libusb_get_device_descriptor(dev, &dev_desc);
    if (ret < 0) {
        std::cout << "failed to get device descriptor: " << libusb_error_name(ret);
        return udev;
    }

    // 配置描述符
    for (int i = 0; i < dev_desc.bNumConfigurations; i++) {
        struct libusb_config_descriptor *config = nullptr;
        int ret = libusb_get_config_descriptor(dev, i, &config);
        if (LIBUSB_SUCCESS != ret) {
            continue;
        }

        // 接口描述符
        for (int j = 0; j < config->bNumInterfaces; j++) {
            const struct libusb_interface *interface = &config->interface[j];
            if (interface->num_altsetting > 0) {
                const struct libusb_interface_descriptor *interface_desc = &interface->altsetting[0];
                udev.interfaceClass = interface_desc->bInterfaceClass;
                break;
            }
        }

        libusb_free_config_descriptor(config);

        if (udev.interfaceClass > 0) {
            break;
        }
    }

    char cbuff[16] = {0};
    sprintf(cbuff, "%04x", dev_desc.idProduct);
    udev.idProduct = cbuff;

    memset(cbuff, 0, sizeof(cbuff));
    sprintf(cbuff, "%04x", dev_desc.idVendor);
    udev.idVendor = cbuff;

    struct libusb_device_handle *usb_p = nullptr;
    libusb_open(dev, &usb_p);
    if (usb_p) {
        unsigned char ucbuff[256] = {0};
        libusb_get_string_descriptor_ascii(usb_p, dev_desc.iManufacturer, ucbuff, sizeof(ucbuff));
        udev.manufacturer = (char *)ucbuff;

        memset(ucbuff, 0, sizeof(ucbuff));
        libusb_get_string_descriptor_ascii(usb_p, dev_desc.iProduct, ucbuff, sizeof(ucbuff));
        udev.product = (char *)ucbuff;

        memset(ucbuff, 0, sizeof(ucbuff));
        libusb_get_string_descriptor_ascii(usb_p, dev_desc.iSerialNumber, ucbuff, sizeof(ucbuff));
        udev.serial = (char *)ucbuff;

        udev.name = udev.manufacturer + " " + udev.product;

        libusb_close(usb_p);
    }

    return udev;
}

usb_device UsbMonitorPrivate::libusb_device_detail(udev_device *dev)
{
    usb_device udev;

    const char *dpath = udev_device_get_devnode(dev);
    const char *idVendor = udev_device_get_sysattr_value(dev, "idVendor");
    const char *idProduct = udev_device_get_sysattr_value(dev, "idProduct");
    const char *serial = udev_device_get_sysattr_value(dev, "serial");
    const char *manufacturer = udev_device_get_sysattr_value(dev, "manufacturer");
    const char *product = udev_device_get_sysattr_value(dev, "product");

    if (!idVendor || !idProduct) {
        return udev;
    }
    udev.idVendor = idVendor;
    udev.idProduct = idProduct;

    if (dpath) {
        udev.dpath = dpath;
        std::cout << "@@@@@" << udev.dpath << "@@@";
    }
    if (manufacturer) {
        udev.manufacturer = manufacturer;
        std::cout << "@@@" << udev.manufacturer << "@@@";
    }
    if (product) {
        udev.product = product;
        std::cout << "@@@" << udev.product << "@@@";
    }
    if (serial) {
        udev.serial = serial;
        std::cout << "@@@" << udev.serial << "@@@";
    }

    udev.name = udev.manufacturer + " " + udev.product;
    std::cout << "@@@" << udev.name << "@@@";

    return udev;
}

int UsbMonitorPrivate::remove_device(const usb_device &udev, const std::string &currentusername)
{
    std::cout << "remove device begin, "
                << "Product ID: " << udev.idProduct
                << ", Vendor ID: " << udev.idVendor
                << ", serial: " << udev.serial
                << ", manufacturer: " << udev.manufacturer
                << ", product: " << udev.product;
    int ret = 0;
    do {
        ret = remove_hotplug_device(udev);
        std::cout << "<remove device>, remove hotplug device: " << ret;
        if (0 == ret)
            break;

        // 重试3次
        for (int i = 0; i < 3; i++) {
            ret = remove_block_device(udev);
            std::cout << "<remove device>, remove block device: " << ret << ", " << i;
            if (0 == ret)
                break;

            boost::this_thread::sleep(boost::posix_time::milliseconds(200));
        }

        if (0 == ret)
            break;

        ret = remove_lowspeed_device(udev, currentusername);
    } while (0);

    std::cout << "remove device end, ret: " << ret;
    return ret;
}

int UsbMonitorPrivate::remove_all_device(const std::string &currentusername)
{
    // 获取当前USB设备列表
    libusb_device **devs = nullptr;
    int ret = libusb_get_device_list(context, &devs);
    if (ret < 0) {
        std::cout << "enumerate usb device failed: " << libusb_error_name(ret);
        return -1;
    }

    // 获取USB设备信息
    std::vector<usb_device> devices;
    for (int i = 0; i < ret; i++) {
        usb_device udev = libusb_device_detail(devs[i]);
        if (udev.idProduct.empty() && udev.idVendor.empty()) { // vid、pid为空
            continue;
        } else if (!udev.is_lowspeed_device() && !udev.is_storage_device()) { // 非usb存储设备
            continue;
        }

        /* 过滤此设备 */
        if (udev.idVendor == "28ed" || udev.serial == "00000000050B" || udev.idProduct == "2021") {
            continue;
        }

        remove_device(udev, currentusername);
    }

    libusb_free_device_list(devs, 1);
    return 0;
}

bool UsbMonitorPrivate::find_device(const usb_device &udev)
{
    std::cout << "find1: " << udev.idVendor << ", " << udev.idProduct;
    std::cout << "find2: " << strtoll(udev.idVendor.c_str(), nullptr, 16) << ", " << strtoll(udev.idProduct.c_str(), nullptr, 16);
    libusb_device_handle *usb_p = libusb_open_device_with_vid_pid(context, strtoll(udev.idVendor.c_str(), nullptr, 16),
                                                                  strtoll(udev.idProduct.c_str(), nullptr, 16));
    if (usb_p) {
        libusb_close(usb_p);
        return true;
    }
    return false;
}

int UsbMonitorPrivate::count_devices()
{
    std::cout << "Begin enumerating usb devices.";

    // 获取当前USB设备列表
    libusb_device **devs = nullptr;
    int ret = libusb_get_device_list(context, &devs);
    if (ret < 0) {
        std::cout << "enumerate usb device failed: " << libusb_error_name(ret);
        return -1;
    }

    // 获取USB设备信息
    std::vector<usb_device> devices;
    for (int i = 0; i < ret; i++) {
        usb_device udev = libusb_device_detail(devs[i]);
        if (udev.is_storage_device() || udev.is_lowspeed_device()) {
            devices.push_back(udev);
        }
    }

    return devices.size();
}

usb_device UsbMonitorPrivate::remove_hotplug_device(udev_device *dev)
{
    usb_device find_udev;
    const char *path = udev_device_get_devnode(dev);
    if (!path) {
        return find_udev;
    }

    boost::mutex::scoped_lock lock(this->udevMutex);
    for (auto it = hotplugDevices.begin(); it != hotplugDevices.end();  ++it) {
        if (it->dpath == path) {
            find_udev = *it;
            hotplugDevices.erase(it);
            break;
        }
    }

    return find_udev;
}

int UsbMonitorPrivate::remove_hotplug_device(const usb_device &udev)
{
    std::string dpath;
    {
        boost::mutex::scoped_lock lock(this->udevMutex);
        for (auto it = hotplugDevices.begin(); it != hotplugDevices.end(); ++it) {
            if (udev.idProduct == it->idProduct
                && udev.idVendor == it->idVendor
                && udev.serial == it->serial) {
                dpath = it->dpath;
                hotplugDevices.erase(it);
                break;
            }
        }
    }

    int ret = 0;
    int fp = 0;
    do {
        if (dpath.empty()) {
            ret = -1;
            break;
        }

        std::cout << "Remove hotplug device, dpath: " << dpath;
        // Open file descriptor to the bus unbind interface
        int fp = open(BUS_UNBIND_INTERFACE, O_WRONLY, 0);
        if (fp == -1) {
            ret = -2;
            break;
        }

        // Grab the path to the device
        const char *path = dpath.c_str();
        char *token = nullptr;
        char *str = strdup(path);
        char *location = nullptr;

        // Grab only the relevant bus location from the path
        while ((token = strsep(&str, "/")) != nullptr) {
            location = strdup(token);
        }

        // create the control block structure
        // and buffer to write
        struct aiocb cb;
        char write_buffer[strlen(location) + 1];
        memset(&cb, 0, sizeof(struct aiocb));
        memcpy(write_buffer, location, strlen(location) + 1);
        cb.aio_nbytes = strlen(location) + 1;
        cb.aio_fildes = fp;
        cb.aio_offset = 0;
        cb.aio_buf = write_buffer;

        // Async-write this location to the unbind interface
        if (aio_write(&cb) == -1) {
            ret = -3;
            break;
        }

        /* Wait until completion */
        while (aio_error(&cb) == EINPROGRESS);

        // Check for errors
        if (aio_error(&cb) != 0) {
            ret = -4;
            break;
        }

        aio_return(&cb);
    } while (0);

    close(fp);
    return ret;
}

int UsbMonitorPrivate::remove_block_device(const usb_device &udev)
{
    int i = 0;
    int ret = 0;
    long int vid = 0, pid = 0;
    struct libusb_device **devs = nullptr;
    struct libusb_device *dev = nullptr;
    struct libusb_device_handle *handler = nullptr;

    vid = strtol(udev.idVendor.c_str(), nullptr, 16);
    pid = strtol(udev.idProduct.c_str(), nullptr, 16);

    std::cout << "will detach device, " << "vid: " << vid << ", pid: " << pid;
    if ((ret = libusb_get_device_list(context, &devs)) < 0) {
        std::cout << "libusb_get_device_list error: " << libusb_error_name(ret) << ", " << udev.manufacturer
                  << ", " << udev.product << ", " << udev.serial << ", " << udev.interfaceClass;
        return -1;
    }

    while ((dev = devs[i++]) != nullptr) {
        struct libusb_device_descriptor desc;
        ret = libusb_get_device_descriptor(dev, &desc);
        if (ret < 0) {
            std::cout << "libusb_get_device_descriptor error: " << libusb_error_name(ret);
            libusb_free_device_list(devs, 1);
            return -1;
        }

        if (desc.idVendor != vid || desc.idProduct != pid) {
            continue;
        }

        ret = libusb_open(dev, &handler);
        if (ret < 0) {
            std::cout << "libusb_open error: " << libusb_error_name(ret);
            continue;
        }

        ret = libusb_detach_kernel_driver(handler, 0);
        if (ret < 0) {
            std::cout << "remove device failed, detach driver error: " << libusb_error_name(ret) << ", " << udev.manufacturer
                      << ", " << udev.product << ", " << udev.serial << ", " << udev.interfaceClass;
        } else {
            libusb_close(handler);
            handler = nullptr;
            return 0;
        }
    }

    return -1;
}

int UsbMonitorPrivate::remove_lowspeed_device(const usb_device &udev, const std::string &currentusername)
{
    usb_device udevTmp(udev);
    // 低速设备，需要附加获取厂商和产品信息
    if (udevTmp.manufacturer.empty() || udevTmp.product.empty()) {
        std::cout << "@@@remove_lowspeed_device --- find_usb_device() begin: " << udevTmp.idProduct
                    << ", " << udevTmp.idVendor << ", " << udevTmp.serial;

        udevTmp = find_usb_device(udev);
        std::cout << "@@@remove_lowspeed_device --- find_usb_device() end: " << udevTmp.idProduct
                    << ", " << udevTmp.idVendor << ", " << udevTmp.serial;
    }

    std::string manufacturer(udevTmp.manufacturer);
    std::string product(udevTmp.product);
    std::string serial(udevTmp.serial);

    boost::replace_all(manufacturer, " ", "_");
    boost::replace_all(product, " ", "_");
    boost::replace_all(serial, " ", "_");

    std::vector<std::string> args = {
        DCMC_DMCG_CLIENT_BASH,
        DCMC_DMCG_CLIENT_ARG0,
        DCMC_DMCG_CLIENT_ARG1,
        DCMC_DMCG_CLIENT_ARG2,
        DEEPIN_GVFS_LUA_SCRIPT,
        DCMC_DMCG_CLIENT_ARGS,
        manufacturer,
        product,
        serial,
        currentusername
    };

    return 0;
//    return utils::executeShellCommand("/bin/sh", args);
}

usb_device UsbMonitorPrivate::find_usb_device(const usb_device &dev)
{
    usb_device udev;

    // 获取当前USB设备列表
    libusb_device **devs = nullptr;
    int ret = libusb_get_device_list(context, &devs);
    if (ret < 0) {
        std::cout << "enumerate usb device failed: " << libusb_error_name(ret);
        return udev;
    }

    // 获取USB设备信息
    std::vector<usb_device> devices;
    for (int i = 0; i < ret; i++) {
        usb_device udevTmp = libusb_device_detail(devs[i]);
        if (dev.idProduct == udevTmp.idProduct && dev.idVendor == udevTmp.idVendor && dev.serial == udevTmp.serial) {
            udev = udevTmp;
            break;
        }
    }

    // USB设备列表销毁
    libusb_free_device_list(devs, 1);

    return udev;
}

} // namespace libusb
} // namespace module
} // namespace dmcg
